package p.lodz.pl.poid.zad2.fourier;

import java.awt.image.BufferedImage;
import p.lodz.pl.poid.zad2.controller.FFTData;
import p.lodz.pl.poid.zad2.controller.FiltersData;

/**
 *
 * @author janiu
 */
public class FourierProcessor {
    
    public BufferedImage phaseTransform(BufferedImage image, Double L, Double K ) {
        FastBitmap fb = new FastBitmap(image);
        fb.toGrayscale();
        FastFourierTransform ft = new FastFourierTransform(fb);
        ft.Forward();        
        ComplexNumber complexNumber[][] = modifyFPhase(ft.getData(), L, K);
        ft.setData(complexNumber);
        ft.Backward();
        FastBitmap output = ft.toFastBitmap();
        output.toRGB();
        return output.toBufferedImage();
    }
    
    public BufferedImage filtersTransform(BufferedImage image, FiltersData data) {        
        FastBitmap fb = new FastBitmap(image);
        fb.toGrayscale();
        FastFourierTransform fft = new FastFourierTransform(fb);
        fft.Forward();
        ComplexNumber complexNumber[][] = null;
        if(data.isLowPassFilterMarked()) {
            complexNumber = lowPassFilter(fft.getData(), data.getMaximumFrequency(), data.getPower());
        }
        if(data.isBandPassFilterMarked()) {
            complexNumber = bandPassFilter(fft.getData(), data.getMinimumBandValue(), data.getMaximumBandValue(), data.getPower());
        }
        fft.setData(complexNumber);       
        fft.Backward();
        fb = fft.toMagnitudeFastBitmap();
        fb.toRGB();
        return fb.toBufferedImage();
    }
    
    public BufferedImage fourierTransform(BufferedImage image, FFTData data) {
        FastBitmap fb = new FastBitmap(image);
        fb.toGrayscale();
        FastFourierTransform fft = new FastFourierTransform(fb);
        if(data.isFftMarked()) {
            fft.Forward();
        }
        if(!data.isDecimationEmpty()) {
            FrequencyFilter frequencyFilter = new FrequencyFilter(0, data.getDecimation());
            frequencyFilter.ApplyInPlace(fft);
        }
        if(data.isIfftMarked()) {
            fft.Backward();
        }
        if(data.isPhaseMarked()) {
            fb = fft.toPhaseFastBitmap();
        }
        if(data.isFrequencyMarked()) {
            fb = fft.toMagnitudeFastBitmap();
        }
        fb.toRGB();
        return fb.toBufferedImage(); 
    }
    
    private ComplexNumber[][] modifyFPhase(ComplexNumber[][] data, double percentageL, double percentageK) {
        ComplexNumber[][] modifiedPhaseSignal = new ComplexNumber[data.length][data[0].length];
        int height = data.length, width = data[0].length;
        percentageL = percentageL / 100.0 * width;
        percentageK = percentageK / 100.0 * height;
        double first, second;
        for (int n = 0; n < height; ++n) {
            for (int m = 0; m < width; ++m) {
                first = (-n * percentageK * 2.0d) / (double) height;
                second = (-m * percentageL * 2.0d) / (double) width;
                modifiedPhaseSignal[n][m] = data[n][m].multiply(ComplexNumber.phaseModification((first + second + (percentageK + percentageL)) * Math.PI));
            }
        }
        return modifiedPhaseSignal;
    }
    

    private ComplexNumber[][] lowPassFilter(ComplexNumber[][] signal, double frequncyMaxValue, double power) {
        ComplexNumber[][] out = new ComplexNumber[signal.length][signal[0].length];

        int height = signal.length, width = signal[0].length;
        frequncyMaxValue = Math.sqrt(Math.pow(height / 2, 2) + Math.pow(width / 2, 2)) * frequncyMaxValue;
        int halfHeight = height / 2, halfWidth = width / 2;
        for (int v = 0; v < height; ++v) {
            for (int u = 0; u < width; ++u) {
                int dv = (v < height / 2) ? v : height - v;
                int du = (u < width / 2) ? u : width - u;
                float dist = (float) Math.sqrt(Math.pow(dv - halfHeight, 2) + Math.pow(du - halfWidth, 2));
                if (power > 0) {
                    out[v][u] = new ComplexNumber(signal[v][u].real, signal[v][u].imaginary);
                    double filter = 1.0d / (1.0d + Math.pow(dist / frequncyMaxValue, 2 * power));
                    out[v][u].real *= filter;
                    out[v][u].imaginary *= filter;
                } else {
                    if (dist > frequncyMaxValue) {
                        out[v][u] = new ComplexNumber(0.0, 0.0);
                    } else {
                        out[v][u] = new ComplexNumber(signal[v][u].real, signal[v][u].imaginary);
                    }
                }
            }
        }

        return out;
    }

    public ComplexNumber[][] bandPassFilter(ComplexNumber[][] signal, double bl, double bh, double power) {
        ComplexNumber[][] out = new ComplexNumber[signal.length][signal[0].length];

        int height = signal.length, width = signal[0].length;
        bl = Math.sqrt(Math.pow(height / 2, 2) + Math.pow(width / 2, 2)) * bl;
        bh = Math.sqrt(Math.pow(height / 2, 2) + Math.pow(width / 2, 2)) * bh;
        int halfHeight = height / 2, halfWidth = width / 2;

        for (int v = 0; v < height; ++v) {
            for (int u = 0; u < width; ++u) {
                int dv = (v < height / 2) ? v : height - v;
                int du = (u < width / 2) ? u : width - u;
                float dist = (float) Math.sqrt(Math.pow(dv - halfHeight, 2) + Math.pow(du - halfWidth, 2));
                if (power > 0) {
                    out[v][u] = new ComplexNumber(signal[v][u].real, signal[v][u].imaginary);
                    double filterlow = 1.0f - 1.0d / (1.0d + Math.pow(dist / bl, 2 * power));
                    double filterhigh = 1.0d / (1.0d + Math.pow(dist / bh, 2 * power));
                    out[v][u].real *= filterlow * filterhigh;
                    out[v][u].imaginary *= filterlow * filterhigh;
                } else {
                    if (dist > bl && dist < bh) {
                        out[v][u] = new ComplexNumber(signal[v][u].real, signal[v][u].imaginary);
                    } else {
                        out[v][u] = new ComplexNumber(0.0, 0.0);
                    }
                }
            }
        }
        return out;
    }
}
